#ifndef TPL_AGENT_GRAPH_H
#define TPL_AGENT_GRAPH_H

#include <iostream>
#include <sys/types.h>
#include <dlink.H>
#include <tpl_concurrent_graph.H>
#include <tpl_dynArray.H>
#include <tpl_arrayQueue.H>

using namespace Aleph;
using namespace std;

namespace Aleph {

#define STOPPED 0
#define RUNNING 1
#define SUSPENDED 2

  template <typename Agent_Info> class Agent;
  
  template <typename Node_Info> class Agent_Node;  
  
  template <typename Arc_Info> class Agent_Arc;
  
  template <typename Arc_Agents, typename Node_Agents, typename Agent_Type> class Agent_Graph;
  
  template <typename Agent_Info> struct Agent   {
    bool suspended;
    Agent_Info info;
    Dlink agent_link;
    Dlink schedule_link;
    Dlink location_link;
    bool in_node;
    void * location;
    void * cookie;
    typedef void (*Transit)(void *, void *, void *);
    Transit transit; 
  public:
    Agent(Agent_Info agent_info, Transit transit) : info (agent_info), transit(transit) { }
    ~Agent(){ }

    typedef Agent_Info Agent_Type;

    void execute(void * graph)
    { 
      transit(graph, this, cookie); 
    }
 
    LINKNAME_TO_TYPE (Agent, agent_link);  
    LINKNAME_TO_TYPE (Agent, schedule_link);
    LINKNAME_TO_TYPE (Agent, location_link);

    Agent_Info & get_info() 
    {
      return info;
    }
  };  
  
  template <typename Agents_Node_Info> 
  struct Agent_Node :  public Concurrent_Node <Agents_Node_Info>
  {
    Dlink agent_list;
    typedef Concurrent_Node<Agents_Node_Info> Parent_Node;
    typedef Agents_Node_Info Node_Type;
    Agent_Node() : Parent_Node() { }
    Agent_Node(const Node_Type & info) : Parent_Node(info) {}
    ~Agent_Node(){ }
  }; 
  
  
  template <typename Agents_Arc_Info> 
  struct Agent_Arc : public Concurrent_Arc<Agents_Arc_Info>    {
    Dlink agent_list;
    typedef Concurrent_Arc<Agents_Arc_Info> Parent_Arc;
    typedef Agents_Arc_Info Arc_Type;
    Agent_Arc() : Parent_Arc() { } 
    virtual ~Agent_Arc(){ } 
  };
  
  template <typename Node,typename Arc, typename Agent > 
  struct Agent_Graph : public Concurrent_Graph<Node, Arc>
  {
  private:
    size_t thread_count;
    pthread_t * threads;
    int status ;
    pthread_mutex_t sch_mutex;
    pthread_mutex_t suspend_mutex;
    pthread_cond_t sch_cond;
    pthread_cond_t suspend_cond;

  public:
    typedef Concurrent_Graph< Node, Arc > Parent_Graph;
    typedef typename Node::Node_Type Node_Type; 
    typedef typename Arc::Arc_Type Arc_Type;
    typedef typename Agent::Agent_Type Agent_Type;
    
    /** Dlinks a las listas de nodos arcos y agentes **/
    
    Dlink agent_list; // lista de agentes
    long num_agents;
    
    Dlink ready_queue; // cola de agentes
    long num_agents_ready; 
    
    Dlink suspended_queue; // cola de agentes
    long num_agents_suspended;  
    
    /** Metodos para los nodos **/ 
   
  public:
   /**Funciones de los agentes **/
    const long get_num_agents() const {return num_agents;}
   
    Node * get_agent_node_location (Agent * agent) 
    {
      if (agent->in_node) 
      {
        return static_cast<Node*>(agent->location);
      }
      return NULL;
    }

    void set_agent_node_location (Agent * agent, Node * node)
    {
      agent->location_link.del();
      agent->in_node = true;
      agent->location = node;
      node->agent_list.append(&agent->location_link);
    }

    Arc * get_agent_arc_location (Agent * agent)
    {
      if (!agent->in_node) 
      {
        return static_cast<Arc*>(agent->location);
      }
      return NULL;
    }

    void set_agent_arc_location(Agent * agent, Arc * arc)
    {
      agent->location_link.del();
      agent->in_node = false;
      agent->location = arc;
      arc->agent_list.append(&agent->location_link);
    }

    bool is_agent_in_node (Agent * agent)
    {
      return agent->in_node;
    }
 

    Agent * get_first_agent ()
    {
      if(get_num_agents() == 0)
	      throw std::range_error("Graph has not agents ");
      return Agent::agent_link_to_Agent (&*agent_list.get_next());
    }
    
    void remove_agent (Agent * agent)
    { 
	    agent->agent_link.del();
      agent->schedule_link.del();
      agent->location_link.del();
	    delete agent;
    }
   
    void clear_agent_list()
    {
      Agent * agent = NULL;
      for (Dlink::Iterator it(&agent_list); it.has_current(); )
      {
        agent = Agent::agent_link_to_Agent(it.get_current());
        it.next();
        remove_agent(agent);
      }
   }

    /**Funciones para el Grafo **/
    const long get_num_agents_ready(){ return num_agents_ready;}
    const long get_num_agents_suspended(){ return num_agents_suspended;}
    
    Agent_Graph(const size_t num_threads) : thread_count(num_threads) 
    {
      threads = (pthread_t*)malloc(num_threads * sizeof(pthread_t));
      init_mutex(sch_mutex);
      init_mutex(suspend_mutex);
      pthread_cond_init(&suspend_cond, NULL);
      pthread_cond_init(&sch_cond, NULL);
      status =RUNNING; 
    }

    virtual ~Agent_Graph()
    {
      clear_agent_list();
      destroy_mutex(sch_mutex);
      destroy_mutex(suspend_mutex);
      pthread_cond_destroy(&suspend_cond);
      pthread_cond_destroy(&sch_cond);
    	free(threads); 
    } 
    
    Agent * create_agent_in_node(Agent_Type agent_type, void (*Fnct)(void *, void *, void *), Node * node, bool suspended = false  )
    {
      Agent * agent = new Agent( agent_type ,  Fnct );
      agent->suspended = suspended;
      
      Dlink * lptr;
      lptr = & agent->schedule_link;
      agent_list.append(&agent->agent_link);
      set_agent_node_location(agent, node);
      if(!suspended)
	    {
        insert_ready_agent(agent);
	    }
      else
	    {
  	    insert_suspended_agent(agent);
 	    }
      
      //1 Crear el objeto Agent(Asignar los valores )
      //2 Colocarlo en la cola correspondiente (meter en la cola del nodo)
      //meter en la cola de ejecucion
      //si hay hilos diponibles despertarlo.
      return agent;
    }
    
    Agent * create_agent_in_arc(Agent_Type * agent_type, void (*Fnct)(void *, void *, void *), Arc * arc, bool suspended = false  )
    {
      // cout<<"Creando agentes en arco"<<endl; 
      Agent  * agent = new Agent ( agent_type ,  Fnct );
      agent->suspended = suspended;
      set_agent_arc_location(agent, arc);
      Dlink * lptr;
      lptr= &  agent.schedule_link;
      agent_list.append(&agent->agent_link);
      if(!suspended)
	    {
        insert_ready_agent(agent);
    	}
      else
	    {
        insert_suspended_agent(agent);
	    }
      //1 Crear el objeto Agent(Asignar los valores )
      //2 Colocarlo en la cola correspondiente (meter en la cola del nodo)
      //meter en la cola de ejecucion
      //si hay hilos diponibles despertarlo.
      return agent;
    }
    
    void suspend_agent(Agent * agent)
    {
      //cambio de status y enviar a la cola de suspendidos
      //retorna un valor si lo suspendió
      remove_ready_agent(agent);
      insert_suspended_agent(agent);
      agent->suspended = true;
    }

    void resume_agent(Agent * agent) 
    {
      remove_suspended_agent(agent);
      insert_ready_agent(agent);
      agent->suspended = false;
    }

    template <class Equal>
    Agent * search_agent(const Agent_Type & agent)
    {
      Agent * result = NULL;
      for (Dlink::Iterator it(agent_list); it.has_current(); it.next()) 
      {
        result = Agent::agent_link_to_Agent(it.get_current());
        if (Equal() (result->get_info(), agent)) 
        {
          return result;
        }
        
      }
      return NULL;
    }
   
    Agent * search_agent(const Agent_Type & agent)
    {
       return search_agent<Aleph::equal_to<Agent_Type> >(agent);
    }

    void start_graph() 
    {
      for (int i = 0; i < thread_count; i++) 
	    {
	      int result = pthread_create(&threads[i] , NULL, Agent_Graph::run, this);
	      if (result == 0) 
		    {
		      cout<<" Thread "<<i<<endl; 
		    }
	    }
    }

    void stop_graph()
    {
      if (get_status() == SUSPENDED)
      {
        resume_graph();
      }
      set_status(STOPPED);
      for (int i = 0; i < thread_count; i++) 
	    {
	      pthread_join(threads[i] , NULL);
      }
    }
    
    int get_status()
    {
      CRITICAL_SECTION(sch_mutex);
      return status;
    }

    void set_status(int value)
    {
      CRITICAL_SECTION(sch_mutex);
      status = value;
    }

    Agent * remove_next_ready_agent() 
    {
      CRITICAL_SECTION(sch_mutex);
 
      if (not ready_queue.is_empty())
	    {
	      Agent * result =  Agent::schedule_link_to_Agent(ready_queue.get_next());
	      ready_queue.remove_next();
	      num_agents_ready--;
	      return result;
	    }
      return NULL;
    }
    

    void insert_ready_agent(Agent* agent)
    {
      CRITICAL_SECTION(sch_mutex);
      ready_queue.append(&agent->schedule_link);
      num_agents_ready++;
      pthread_cond_signal(&sch_cond);
    }
    
    void remove_ready_agent(Agent * agent)
    {
      CRITICAL_SECTION(sch_mutex);
      agent->schedule_link.del();
    }
   
    void insert_suspended_agent(Agent * agent)
    {
      CRITICAL_SECTION(sch_mutex);
 	    suspended_queue.append(&agent->schedule_link);
	    num_agents_suspended++; 
    }

    void remove_suspended_agent(Agent * agent)
    {
      CRITICAL_SECTION(sch_mutex);
      agent->schedule_link.del();
    }

    void suspend_graph()
    {
      set_status(SUSPENDED);
    }
    void resume_graph()
    {
      set_status(RUNNING);
      pthread_cond_broadcast(&suspend_cond);

    }
    
    static void * run(void * cookie) 
    {
      Agent_Graph * graph = static_cast<Agent_Graph*>(cookie);
      // Lógica de ejecución de agentes
      while (graph->get_status() != STOPPED)
      {
        while (graph->get_status() == SUSPENDED)
        {
          pthread_mutex_lock(&graph->suspend_mutex);
          pthread_cond_wait(&graph->suspend_cond, &graph->suspend_mutex);
          pthread_mutex_unlock(&graph->suspend_mutex);
        }
        // Agregar codigo de ejecucion aqui
	      Agent * next_agent = NULL;
        do {
          next_agent = graph->remove_next_ready_agent();
          if (next_agent == NULL) 
          {
            pthread_mutex_lock(&graph->sch_mutex);
            pthread_cond_wait(&graph->sch_cond, &graph->sch_mutex);
            pthread_mutex_unlock(&graph->sch_mutex);
          }
	      } while (next_agent == NULL);

  	    next_agent->execute(graph);
	      graph->insert_ready_agent(next_agent);
      }
      return NULL;
    }
  };
}
#endif 
